import numpy as np
import pandas as pd


class Core:
    def __init__(self, **kwargs):
        self.replenishment_cycle = kwargs.get('replenishment_cycle', 7)    # 补货周期，默认7天
        self.safety_inventory_quantity = None    # 安全库存
        self.instock_inventory_quantity = None   # 实际库存
        self.avg_sales_quantity = None          # 预测的平均销量

    @property
    def dynamic_sales_days(self):
        return self.instock_inventory_quantity / self.avg_sales_quantity if self.avg_sales_quantity else 0

    @property
    def safety_inventory_days(self):
        return self.safety_inventory_quantity / self.avg_sales_quantity if self.avg_sales_quantity else 0

    @property
    def is_replenishment(self):
        return self.replenishment_cycle + self.safety_inventory_days >= self.dynamic_sales_days

    @property
    def order_quantity(self):
        if not self.is_replenishment:
            return 0
        return max(0, self.replenishment_cycle * self.avg_sales_quantity + 
                  self.safety_inventory_quantity - self.instock_inventory_quantity)

    def data_cleaning(self):
        """数据清洗，子类必须实现"""
        raise NotImplementedError

    def fitting(self):
        """拟合模型，子类必须实现"""
        raise NotImplementedError

    def predicting(self, instock_inventory):
        """
        预测并计算补货量
        :param instock_inventory: DataFrame，包含product_code和instock_quantity
        :return: DataFrame，包含补货建议
        """
        # # 获取预测的平均销量
        # result_df = self.fitting()
        #
        # # 合并库存数据
        # result_df = result_df.merge(
        #     instock_inventory,
        #     on='product_code',
        #     how='left'
        # )
        
        # 计算安全库存
        _z = 1.96  # 95%置信度
        _l = self.replenishment_cycle  # 固定7天
        instock_inventory['safety_inventory_quantity'] = (
            _z * np.sqrt(_l * instock_inventory['demand_variance'])
        )
        
        # 应用补货计算逻辑
        for idx, row in instock_inventory.iterrows():
            # 设置实例属性
            self.avg_sales_quantity = row['avg_sales_quantity']
            self.safety_inventory_quantity = row['safety_inventory_quantity']
            self.instock_inventory_quantity = row['instock_quantity']
            
            # 计算补货相关指标
            instock_inventory.at[idx, 'dynamic_sales_days'] = self.dynamic_sales_days
            instock_inventory.at[idx, 'safety_inventory_days'] = self.safety_inventory_days
            instock_inventory.at[idx, 'is_replenishment'] = self.is_replenishment
            instock_inventory.at[idx, 'order_quantity'] = self.order_quantity
        
        return instock_inventory[instock_inventory['order_quantity'] > 0]


class MovingAverage(Core):
    def __init__(self, product_sales, **kwargs):
        super().__init__(**kwargs)
        self.prd_sls = product_sales

    def data_cleaning(self):
        # 首先对读取到的数据进行分组统计
        group_keys = ['order_date','product_code']
        indicators = ['quantity']

        df = self.prd_sls
        # 按日期和产品分组求和
        df = df.groupby(group_keys)[*indicators].sum().reset_index()

        # 创建日期范围（这样更高效）
        date_range = pd.date_range(
            start=df['order_date'].min(),
            end=df['order_date'].max(),
            freq='D'
        )

        # 获取所有唯一的product_code
        product_codes = df['product_code'].unique()

        # 创建笛卡尔积（这比循环效率高）
        dates_df = pd.DataFrame(date_range, columns=['order_date'])
        products_df = pd.DataFrame(product_codes, columns=['product_code'])
        full_dates = dates_df.assign(key=1).merge(
            products_df.assign(key=1), 
            on='key'
        ).drop('key', axis=1)
        full_dates['order_date'] = pd.to_datetime(full_dates['order_date']).dt.date
        
        # 确保原始数据的order_date也是date类型
        df['order_date'] = pd.to_datetime(df['order_date']).dt.date

        # 与原始数据合并，填充缺失值
        result = pd.merge(
            full_dates, 
            df, 
            on=['order_date', 'product_code'], 
            how='left'
        )
        result['quantity'] = result['quantity'].fillna(0)

        # 按产品代码分组，获取销量列表
        quantity_df = result.groupby('product_code')['quantity'].apply(list).reset_index()
        
        return quantity_df
    
    def fitting(self):
        # 对每个产品的销量列表进行移动平均处理
        quantity_df = self.data_cleaning()
        window_size = 7
        
        def calculate_stats(quantity_list):
            # 计算可以完整分成的块数
            n = len(quantity_list)
            complete_chunks_count = n // window_size
            
            if complete_chunks_count == 0:
                return {'mean': 0, 'variance': 0}
                
            # 只取完整的块
            valid_length = complete_chunks_count * window_size
            valid_data = quantity_list[n - valid_length:]  # 从后往前取，保留最新数据
            
            # 转换为numpy数组并reshape成矩阵形式
            chunks = np.array(valid_data).reshape(-1, window_size)
            
            # 计算每个块的平均值
            chunk_means = np.mean(chunks, axis=1)
            
            # 计算非零块的统计量
            non_zero_means = chunk_means[chunk_means > 0]
            if len(non_zero_means) == 0:
                return {'mean': 0, 'variance': 0}
                
            return {
                'mean': np.mean(non_zero_means),
                'variance': np.var(non_zero_means) if len(non_zero_means) > 1 else 0
            }
        
        # 计算统计量
        stats = quantity_df['quantity'].apply(calculate_stats)
        quantity_df['avg_sales_quantity'] = stats.apply(lambda x: x['mean'])
        quantity_df['demand_variance'] = stats.apply(lambda x: x['variance'])
        
        return quantity_df
